﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using AwesomeEnginePC.Physics;
using AwesomeEnginePC.Extentions;

namespace AwesomeEnginePC.Graphics.TwoDimensional.Sprites
{
    public class SpriteSheet : BaseSprite
    {
        private int _tileWidth;
        private int _tileHeight;
        private int _tilesInRow;

        private List<int> _framesToPlay;
        private int _currentPlayingIndex;

        private int _currentFrameIndex;
        private int _maxFrames;

        private TimeSpan _previousTime;
        private TimeSpan _timeBetweenFrames;

        private Rectangle _tileRec;

        // Support for Sprite that use Sprite Sheets
        private bool UsesSpriteSheet = false;
        private Rectangle CustomSourceRectangle;
        private List<Rectangle> CustomSheetRectanglesList;
        private int CustomSheetRectangleListIndex = 0;

        /// <summary>
        /// Gets if the SpriteSheet is automatically playing.
        /// </summary>
        public bool IsPlaying
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets the current frame index of the SpriteSheet.
        /// </summary>
        public int CurrentFrameIndex
        {
            get { return _currentFrameIndex; }
        }

        /// <summary>
        /// Gets the height of the rendered image.
        /// </summary>
        public override int Height
        {
            get 
            {
                if (UsesSpriteSheet)
                {
                    _tileHeight = CustomSourceRectangle.Height;
                }
                return (int)(_tileHeight * ScalingY);
            }
        }

        /// <summary>
        /// Gets the width of the rendered image.
        /// </summary>
        public override int Width
        {
            get 
            {
                if (UsesSpriteSheet)
                {
                    _tileWidth = CustomSourceRectangle.Width;
                }
                return (int)(_tileWidth * ScalingX);
            }
        }

        /// <summary>
        /// Gets the raw height (no scaling) of the image.
        /// </summary>
        public override int RawHeight
        {
            get
            {
                return _tileHeight;
            }
        }

        /// <summary>
        /// Gets the raw width (no scaling) of the image.
        /// </summary>
        public override int RawWidth
        {
            get
            {
                return _tileWidth;
            }
        }
        public List<Rectangle> CustomSheetList
        {
            get
            {
                return CustomSheetRectanglesList;
            }
        }

        public int CustomSheetCount
        {
            get
            {
                return CustomSheetRectanglesList.Count - 1;
            }
        }

        public int CustomSheetIndex
        {
            get
            {
                return CustomSheetRectangleListIndex;
            }
            set
            {
                CustomSheetRectangleListIndex = value;
            }
        }

        /// <summary>
        /// Creates a new SpriteSheet Object. Imports Content and info about tile sheet.
        /// </summary>
        /// <param name="CM">The ContentManager to load the content from.</param>
        /// <param name="AssetName">The AssetName to load.</param>
        /// <param name="tileDimentions">The dimensions of the tiles in pixels.</param>
        public SpriteSheet(ContentManager CM, string AssetName, Vector tileDimentions)
            : base(CM, AssetName)
        {
            _tileWidth = (int)tileDimentions.X;
            _tileHeight = (int)tileDimentions.Y;

            _tilesInRow = base.Width / _tileWidth;

            _currentFrameIndex = 0;
            _maxFrames = (SpriteTexture.Width / (int)tileDimentions.X) * (SpriteTexture.Height / (int)tileDimentions.Y);

            ChangeFrame(0);
        }

        /// <summary>
        /// Creates a new SpriteSheet Object. Imports Content and info about tile sheet.
        /// </summary>
        /// <param name="CM">The ContentManager to load the content from.</param>
        /// <param name="AssetName">The AssetName to load.</param>
        /// <param name="TileWidth">The width, in pixels, of a single tile.</param>
        /// <param name="TileHeight">The height, in pixels, of a single tile.</param>
        public SpriteSheet(ContentManager CM, string AssetName, int TileWidth, int TileHeight)
            : base(CM, AssetName)
        {
            _tileWidth = TileWidth;
            _tileHeight = TileHeight;

            _tilesInRow = base.Width / _tileWidth;

            _currentFrameIndex = 0;
            _maxFrames = (SpriteTexture.Width / _tileWidth) * (SpriteTexture.Height / _tileHeight);

            ChangeFrame(0);
        }

        public void InitalizeForCustomSpriteSheet()
        {
            UsesSpriteSheet = true;
            CustomSourceRectangle = new Rectangle();
            CustomSheetRectanglesList = new List<Rectangle>();
        }

        public virtual void UpdateCustomSourceRectangle()
        {
            CustomSourceRectangle = CustomSheetRectanglesList[CustomSheetRectangleListIndex];
        }

        /// <summary>
        /// Changes the current frame of the sprite.
        /// </summary>
        /// <param name="index">The index to change the sprite to.</param>
        public virtual void ChangeFrame(int index)
        {
            if ((index < 0) || (index >= _maxFrames))
            {//if the index is out of bounds, do nothing
                return;
            }

            int Xtileindex = 0;
            int Ytileindex = 0;

            Xtileindex = index % _tilesInRow;
            Ytileindex = index / _tilesInRow;

            _tileRec.X = Xtileindex * _tileWidth;
            _tileRec.Y = Ytileindex * _tileHeight;
            _tileRec.Width = _tileWidth;
            _tileRec.Height = _tileHeight;

            _currentFrameIndex = index;
        }

        /// <summary>
        /// Changes the frame sequentially.
        /// </summary>
        /// <param name="amount">The number to increment the frame by. Can be either positive or negative.</param>
        public virtual void SequentialFrameChange(int amount)
        {
            if (amount == 0) { return; }

            _currentFrameIndex += amount;

            while (_currentFrameIndex < 0)
            {
                _currentFrameIndex += _maxFrames;
            }

            while (_currentFrameIndex >= _maxFrames)
            {
                _currentFrameIndex -= _maxFrames;
            }

            ChangeFrame(_currentFrameIndex);
        }

        /// <summary>
        /// Starts playing the SpriteSheet sequential using a given time between frames. Also ignores any frame indexes specified.
        /// </summary>
        /// <param name="timeBetweenFrames">The time between frames.</param>
        /// <param name="frameIndexesToIgnore">A list of frames to not play.</param>
        public void Play(TimeSpan timeBetweenFrames, params int [] frameIndexesToIgnore)
        {
            if (!CheckToSeeThatIndexesAreValid(frameIndexesToIgnore.ToList()))
            {
                throw new Exception("Frames to be ignored are not valid.");
            }

            List<int> indexes = new List<int>();

            for (int i = 0; i < _maxFrames; i++)
            {
                if (!frameIndexesToIgnore.Contains(i))
                {
                    indexes.Add(i);
                }
            }

            _framesToPlay = new List<int>(indexes);

            _timeBetweenFrames = timeBetweenFrames;

            IsPlaying = true;
        }

        /// <summary>
        /// Starts playing the SpriteSheet in a given order using a give time between frames.
        /// </summary>
        /// <param name="timeBetweenFrames">Time time between frames.</param>
        /// <param name="framesToPlay">The order of frames to play.</param>
        public void Play(TimeSpan timeBetweenFrames, List<int> framesToPlay)
        {
            //frames input check
            if (!CheckToSeeThatIndexesAreValid(framesToPlay))
            {
                throw new Exception("Frames entered are not valid.");
            }

            _framesToPlay = new List<int>(framesToPlay);
            _timeBetweenFrames = timeBetweenFrames;
            IsPlaying = true;
        }

        private bool CheckToSeeThatIndexesAreValid(List<int> framesToPlay)
        {
            int invalidFrameIndexes = (from frame in framesToPlay
                                       where !frame.IsWithinRange(0, _maxFrames - 1)
                                       select frame).Count();

            return invalidFrameIndexes == 0;
        }

        /// <summary>
        /// Stops automatic playback of the sprite sheet.
        /// </summary>
        public void Stop(bool goToFirstFrame = false)
        {
            IsPlaying = false;

            if (goToFirstFrame)
            {
                ChangeFrame(0);
            }
        }

        /// <summary>
        /// Allows the SpriteSheet to update. Only needed when playing the object.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (_previousTime == null)
            {
                _previousTime = new TimeSpan(gameTime.TotalGameTime.Ticks);
            }

            if (IsPlaying && gameTime.TotalGameTime - _previousTime >= _timeBetweenFrames)
            {
                //play next frame
                _currentPlayingIndex += 1;
                if (_currentPlayingIndex >= _framesToPlay.Count)
                {
                    _currentPlayingIndex = 0;
                }

                ChangeFrame(_framesToPlay[_currentPlayingIndex]);

                _previousTime = new TimeSpan(gameTime.TotalGameTime.Ticks);
            }
        }

        /// <summary>
        /// Draws the SpriteSheet with the current frame.
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="SB"></param>
        public override void Draw(GameTime gameTime, SpriteBatch SB)
        {
            if (Active)
            {
                if (UsesSpriteSheet)
                {
                    UpdateCustomSourceRectangle();
                    SB.Draw(SpriteTexture, Position.ToVector2(), CustomSourceRectangle, Tint,
                                Rotation, Origin.ToVector2(), new Vector2(ScalingX, ScalingY), EffectsOnSprite, LayerDepth);
                }
                else
                {

                    SB.Draw(SpriteTexture, Position.ToVector2(), _tileRec, Tint, Rotation, Origin.ToVector2(), new Vector2(ScalingX, ScalingY), EffectsOnSprite, LayerDepth);
                }
                
            }
        }

        
    }
}
